明确的概念
目前主流的日志框架是logback,log4j,javalogging,这是目前主流的日志框架,其中出了个适配器,相当于接口,slf4j的作者和logback,log4j是一个人,这个接口可以适配logback,log4j
springboot启动时如何选择日志的呢
我们从springboot启动
预先知识:
- @Import,这个注解的value是Class类型的数组,我们可以直接传递一些类进去,那么这些类就会被导入到当前ioc容器。或者导入,这些类里面加了@Bean方法返回的对象。同时会将实现ImportSelector接口的selectImports方法返回的类的全限定名导入到ioc容器。
流程如下:
1.启动类上面的@SpringBootApplication注解,点进去,再进去EnableAutoConfiguration注解,上面有个import注解,前面说过了,点进去这个AutoConfigurationImportSelector类,看到selectImports方法:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
我们看到第2行代码,点进去
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
我们点到第五行代码点进去
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
不断深入SpringFactoriesLoader.loadFactoryNames方法
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
其中FACTORIES_RESOURCE_LOCATION= “META-INF/spring.factories”
也就是说从当前jar将前面说的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的value全部拉出来然后反射实例化后注入到spring ioc容器。这些key都是自动化配置的key。再看看后面的步骤。
2。从SpringApplication.run(WebApplication.class, args)
进入到
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
这个方法里面创建了SpringApplication对象,进入到其构造方法。
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
在setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));这行代码里面Class类型是ApplicationListener。这个方法前文讲述过了。这里特别提一下其中有个Listener和本文相关,那就是LoggingApplicationListener这个Listener。然后将其注入到SpringApplication的listeners属性里面。前面方法里面创建对象后立马调用了run方法。进去,这个run方法我就不细说了,都知道。
try {
listeners.running(context);
}
这行代码最终是启动listeners的,点进去源码可以看到
@Override
public void running(ConfigurableApplicationContext context) {
context.publishEvent(
new ApplicationReadyEvent(this.application, this.args, context));
}
然后我们进入到LoggingApplicationListener这个类的关键代码处
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartingEvent) {
onApplicationStartingEvent((ApplicationStartingEvent) event);
}
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
.getApplicationContext().getParent() == null) {
onContextClosedEvent();
}
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
执行第一个事件。
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
this.loggingSystem = LoggingSystem
.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
进入LoggingSystem的get方法里面。
public static LoggingSystem get(ClassLoader classLoader) {
String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
if (StringUtils.hasLength(loggingSystem)) {
if (NONE.equals(loggingSystem)) {
return new NoOpLoggingSystem();
}
return get(classLoader, loggingSystem);
}
return SYSTEMS.entrySet().stream()
.filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
.map((entry) -> get(classLoader, entry.getValue())).findFirst()
.orElseThrow(() -> new IllegalStateException(
"No suitable logging system located"));
}
核心出来了在这里,从SYSTEMS这个map里面按顺序取值,而这个map的值是下面的。测试了下。顺序取出的顺序是logback,log4j,javalog。按照上面代码的逻辑,如果上下文存在这个日志类,那么springboot就使用哪个类。显然是优先使用logback
private static final Map<String, String> SYSTEMS;
static {
Map<String, String> systems = new LinkedHashMap<>();
systems.put("ch.qos.logback.core.Appender",
"org.springframework.boot.logging.logback.LogbackLoggingSystem");
systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
"org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
systems.put("java.util.logging.LogManager",
"org.springframework.boot.logging.java.JavaLoggingSystem");
SYSTEMS = Collections.unmodifiableMap(systems);
}